package com.zhuhjay.service.impl;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhuhjay.config.SystemConfig;
import com.zhuhjay.enums.HttpCode;
import com.zhuhjay.mapper.SystemFileMapper;
import com.zhuhjay.model.dto.FileChunkDto;
import com.zhuhjay.model.entity.SystemFile;
import com.zhuhjay.model.vo.CheckChunkVo;
import com.zhuhjay.service.SystemFileService;
import com.zhuhjay.utils.BeanBuilder;
import com.zhuhjay.utils.FileUtils;
import com.zhuhjay.utils.result.ResultMap;
import com.zhuhjay.utils.SecurityContextHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.zhuhjay.constant.RedisConstant.FILE_UPLOAD_PREFIX;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Zhuhjay
 * @since 2023-02-07
 */
@Service
public class SystemFileServiceImpl extends ServiceImpl<SystemFileMapper, SystemFile> implements SystemFileService {

    @Resource
    private SystemConfig systemConfig;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public CheckChunkVo check(String identifier) {
        Long count = this.lambdaQuery()
                .eq(SystemFile::getIdentifier, identifier)
                .count();
        // 文件已上传
        if (count != null && count > 0) {
            return BeanBuilder.builder(CheckChunkVo::new)
                    .with(CheckChunkVo::setUploaded, true)
                    .build();
        }
        // 查看是否存在未上传完的文件
        String key = FILE_UPLOAD_PREFIX + identifier;
        Set<String> members = stringRedisTemplate.opsForSet().members(key);
        if (members != null && members.size() > 0) {
            List<Integer> chunks = members.stream()
                    .map(Integer::parseInt).collect(Collectors.toList());
            return BeanBuilder.builder(CheckChunkVo::new)
                    .with(CheckChunkVo::setUploaded, false)
                    .with(CheckChunkVo::setUploadedChunks, chunks)
                    .build();
        }
        // 文件尚未上传
        return BeanBuilder.builder(CheckChunkVo::new)
                .with(CheckChunkVo::setUploaded, false)
                .build();
    }

    @Override
    public ResultMap upload(FileChunkDto chunkInfo) {
        MultipartFile multipartFile = chunkInfo.getFile();
        if (FileUtils.isEmpty(multipartFile)) {
            return ResultMap.fail("文件为空");
        }
        final String uploadPath = systemConfig.getUploadPath();
        File upload = FileUtil.mkdir(uploadPath);
        String fileName = upload + "/" + chunkInfo.getIdentifier();
        return chunkInfo.getTotalChunks() == 1 ?
                singletonUploadFile(fileName, chunkInfo) :
                shardingUploadFile(fileName, chunkInfo);
    }

    /** 单文件分片上传 **/
    private ResultMap singletonUploadFile(String fileName, FileChunkDto chunkInfo) {
        try {
            // TODO: 斟酌是否后续改用 FileChannel
            FileUtil.writeBytes(chunkInfo.getFile().getBytes(), fileName);
            saveToDb(chunkInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return ResultMap.fail(HttpCode.SERVER_ERROR);
        }
        return ResultMap.ok();
    }

    /** 多文件分片上传 **/
    private ResultMap shardingUploadFile(String fileName, FileChunkDto chunkInfo) {
        MultipartFile multipartFile = chunkInfo.getFile();
        // TODO: 斟酌是否后续改用 FileChannel
        try (RandomAccessFile file = new RandomAccessFile(fileName, "rw")) {
            long chunkSize = chunkInfo.getChunkSize();
            long offset = (chunkInfo.getChunkNumber() - 1) * chunkSize;
            file.seek(offset);
            file.write(multipartFile.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
            return ResultMap.fail(HttpCode.SERVER_ERROR);
        }

        // 保存分片信息
        String key = FILE_UPLOAD_PREFIX + chunkInfo.getIdentifier();
        stringRedisTemplate.opsForSet().add(key, String.valueOf(chunkInfo.getChunkNumber()));

        // 检查是否上传完毕
        Long count = stringRedisTemplate.opsForSet().size(key);

        // 如果分片都还没有上传齐
        if (count == null || count.intValue() != chunkInfo.getTotalChunks()) {
            return ResultMap.ok();
        }

        // 分片都上传完毕
        if (saveToDb(chunkInfo)) {
            // 删除分片记录
            stringRedisTemplate.delete(key);
            return ResultMap.ok();
        } else {
            return ResultMap.fail(HttpCode.SERVER_ERROR);
        }
    }

    /** 将文件信息保存到数据库中 **/
    private boolean saveToDb(FileChunkDto chunkInfo) {
        MultipartFile multipartFile = chunkInfo.getFile();
        return this.save(BeanBuilder.builder(SystemFile::new)
                .with(SystemFile::setRealName, multipartFile.getOriginalFilename())
                .with(SystemFile::setIdentifier, chunkInfo.getIdentifier())
                .with(SystemFile::setType, multipartFile.getContentType())
                .with(SystemFile::setSize, chunkInfo.getTotalSize())
                .with(SystemFile::setCreateUserId, SecurityContextHolder.getLoginUserDetail().getId())
                .build());
    }
}
